/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

// #ifdef __WITH_UTILITIES

/**
 * Opens a window with the string in it.
 * @param {String} str The HTML string to display in the new window.
 */
ppc.pasteWindow = function(str){
    var win = window.open("about:blank");
    win.document.write(str);
};

//#ifdef __WITH_ENTITY_ENCODING

/**
 * Escapes HTML from a string.
 * @param {String} str The html to be escaped.
 * @return {String} The escaped string.
 */
ppc.htmlentities = function(str){
    return str.escapeHTML();
};

/**
 * Escape an XML string, making it ascii compatible.
 * @param {String} str The xml string to escape.
 * @return {String} The escaped string.
 * @method xmlentities
 *
 */
 /* @todo This function does something completely different from htmlentities, 
 *       the name is confusing and misleading.
 */
ppc.xmlentities = ppc.escapeXML;

/**
 * Unescape an HTML string.
 * @param {String} str The string to unescape.
 * @return {String} The unescaped string.
 */
ppc.html_entity_decode = function(str){
    return (str || "").replace(/\&\#38;/g, "&").replace(/&lt;/g, "<")
        .replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&nbsp;/g, " ");
};

//#endif

/**
 * Determines whether the keyboard input was a character that can influence
 * the value of an element (like a textbox).
 * @param {Number} charCode The ascii character code
 * @returns {Boolean} `true` if it was a character
 */
ppc.isCharacter = function(charCode){
    return (charCode < 112 || charCode > 122)
      && (charCode == 32 || charCode > 42 || charCode == 8);
};

/**
 * This random number generator has been added to provide a more robust and
 * reliable random number spitter than the native Ecmascript Math.random()
 * function.
 * 
 * It is an implementation of the Park-Miller algorithm. (See 'Random Number
 * Generators: Good Ones Are Hard to Find', by Stephen K. Park and Keith W.
 * Miller, Communications of the ACM, 31(10):1192-1201, 1988.)
 * @class ppc.randomGenerator
 * @author David N. Smith of IBM's T. J. Watson Research Center.
 * @author Mike de Boer (mike AT c9 DOT io)
 * 
 */
ppc.randomGenerator = {
    d: new Date(),
    seed: null,
    A: 48271,
    M: 2147483647,
    Q: null,
    R: null,
    oneOverM: null,

    /**
     * Generates a random [[Number]] between a lower and upper boundary.
     * The algorithm uses the system time, in minutes and seconds, to 'seed'
     * itself, that is, to create the initial values from which it will generate
     * a sequence of numbers. If you are familiar with random number generators,
     * you might have reason to use some other value for the seed. Otherwise,
     * you should probably not change it.
     * @param {Number} lnr Lower boundary
     * @param {Number} unr Upper boundary
     * @returns A random number between `lnr` and`unr`
     * @type Number
     */
    generate: function(lnr, unr) {
        if (this.seed == null)
            this.seed = 2345678901 + (this.d.getSeconds() * 0xFFFFFF) + (this.d.getMinutes() * 0xFFFF);
        this.Q = this.M / this.A;
        this.R = this.M % this.A;
        this.oneOverM = 1.0 / this.M;
        return Math.floor((unr - lnr + 1) * this.next() + lnr);
    },

    /**
     * Returns a new random number, based on the 'seed', generated by the
     * [[ppc.randomGenerator.generate]] method.
     * @type Number
     */
    next: function() {
        var hi = this.seed / this.Q;
        var lo = this.seed % this.Q;
        var test = this.A * lo - this.R * hi;
        if (test > 0)
            this.seed = test;
        else
            this.seed = test + this.M;
        return (this.seed * this.oneOverM);
    }
};

/**
 * Adds a time stamp to the URL to prevent the browser from caching it.
 * @param {String} url The URL to add the timestamp to.
 * @return {String} The url... with a timestamp!
 */
ppc.getNoCacheUrl = function(url){
    return url
        + (url.indexOf("?") == -1 ? "?" : "&")
        + "nocache=" + new Date().getTime();
};

/**
 * Checks if the string contains curly braces at the start and end. If so, it's
 * processed as Javascript via `eval()`. Otherwise, the original string is returned.
 * @param {String} str The string to parse.
 * @return {String} The result of the parsing.
 */
ppc.parseExpression = function(str){
    if (!ppc.parseExpression.regexp.test(str))
        return str;

    //#ifdef __DEBUG
    try {
    //#endif
        return eval(RegExp.$1);
    //#ifdef __DEBUG
    }
    catch(e) {
        throw new Error(ppc.formatErrorString(0, null,
            "Parsing Expression",
            "Invalid expression given '" + str + "'"));
    }
    //#endif
};
ppc.parseExpression.regexp = /^\{([\s\S]*)\}$/;

/**
 * @private
 */
ppc.formatNumber = function(num, prefix){
    var nr = parseFloat(num);
    if (!nr) return num;

    var str = new String(Math.round(nr * 100) / 100).replace(/(\.\d?\d?)$/, function(m1){
        return m1.pad(3, "0", ppc.PAD_RIGHT);
    });
    if (str.indexOf(".") == -1)
        str += ".00";

    return prefix + str;
};

/**
 * Execute a script in the global scope.
 *
 * @param {String} str  The JavaScript code to execute
 * @param {Object} [win] A reference to a window
 * @return {String} The executed JavaScript code
 */
ppc.jsexec = function(str, win){
    if (!str)
        return str;
    if (!win)
        win = self;

    if (ppc.isO3)
        eval(str, self);
    else if (ppc.hasExecScript) {
        win.execScript(str);
    }
    else {
        var head = win.document.getElementsByTagName("head")[0];
        if (head) {
            var script = win.document.createElement('script');
            script.setAttribute('type', 'text/javascript');
            script.text = str;
            head.appendChild(script);
            head.removeChild(script);
        } else
            eval(str, win);
    }

    return str;
};

/*
 * Shorthand for an empty function.
 */
ppc.K = function(){};

// #ifdef __WITH_ECMAEXT

/**
 * Reliably determines whether a variable is a Number.
 *
 * @param {Mixed}   value The variable to check
 * @type  {Boolean} `true` if the argument is a number
 */
ppc.isNumber = function(value){
    return parseFloat(value) == value;
};

/**
 * Reliably determines whether a variable is an array. For more information, see 
 * <http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/>
 *
 * @param {Mixed}   value The variable to check
 * @type  {Boolean} `true` if the argument is an array
 */
ppc.isArray = function(value) {
    return Object.prototype.toString.call(value) === "[object Array]";
};

/**
 * Determines whether a string is true (in the HTML attribute sense).
 * @param {Mixed} value The variable to check. Possible truth values include:
 *  - true  
 *  - 'true'
 *  - 'on'  
 *  - 1     
 *  - '1'   
 * @return {Boolean} Whether the string is considered to imply truth.
 */
ppc.isTrue = function(c){
    return (c === true || c === "true" || c === "on" || typeof c == "number" && c > 0 || c === "1");
};

/**
 * Determines whether a string is false (in the HTML attribute sense).
 * @param {Mixed} value The variable to check. Possible false values include:
 *   - false   
 *   - 'false' 
 *   - 'off'   
 *   - 0       
 *   - '0'     
 * @return {Boolean} whether the string is considered to imply untruth.
 */
ppc.isFalse = function(c){
    return (c === false || c === "false" || c === "off" || c === 0 || c === "0");
};

/**
 * Determines whether a value should be considered false. This excludes, amongst
 * others, the number 0.
 * @param {Mixed} value The variable to check
 * @return {Boolean} Whether the variable is considered false.
 */
ppc.isNot = function(c){
    // a var that is null, false, undefined, Infinity, NaN and c isn't a string
    return (!c && typeof c != "string" && c !== 0 || (typeof c == "number" && !isFinite(c)));
};

/**
 * Creates a relative URL based on an absolute URL.
 * @param {String} base The start of the URL to which relative URL's work.
 * @param {String} url  The URL to transform.
 * @return {String} The relative URL.
 */
ppc.removePathContext = function(base, url){
    if (!url)  return "";

    if (url.indexOf(base) > -1)
        return url.substr(base.length);

    return url;
};

/*
 * @private
 * @todo why is this done like this?
 */
ppc.cancelBubble = function(e, o, noPropagate){
    if (e.stopPropagation)
        e.stopPropagation()
    else 
        e.cancelBubble = true;
    // #ifdef __WITH_FOCUS
    //if (o.$focussable && !o.disabled)
        //ppc.window.$focus(o);
    // #endif
    
    /*if (ppc.isIE)
        o.$ext.fireEvent("on" + e.type, e);
    else 
        o.$ext.dispatchEvent(e.name, e);*/
    
    if (!noPropagate) {
        if (o && o.$ext && o.$ext["on" + (e.type || e.name)])
            o.$ext["on" + (e.type || e.name)](e);
        ppc.window.$mousedown(e);
    }
    //if (ppc.isGecko)
        //ppc.window.$mousedown(e);
    
    //#ifdef __ENABLE_UIRECORDER_HOOK
    if (ppc.uirecorder && ppc.uirecorder.captureDetails 
      && (ppc.uirecorder.isRecording || ppc.uirecorder.isTesting)) {
        ppc.uirecorder.capture.nextStream(e.type || e.name);
    }
    //#endif
};

// #endif

/*
 * Attempt to fix memory leaks
 * @private
 */
ppc.destroyHtmlNode = function (element) {
    if (!element) return;

    if (!ppc.isIE || element.ownerDocument != document) {
        if (element.parentNode)
            element.parentNode.removeChild(element);
        return;
    }

    var garbageBin = document.getElementById('IELeakGarbageBin');
    if (!garbageBin) {
        garbageBin    = document.createElement('DIV');
        garbageBin.id = 'IELeakGarbageBin';
        garbageBin.style.display = 'none';
        document.body.appendChild(garbageBin);
    }

    // move the element to the garbage bin
    garbageBin.appendChild(element);
    garbageBin.innerHTML = '';
};

//#ifdef __WITH_SMARTBINDINGS
/**
 * @private
 */
ppc.getRules = function(node){
    var rules = {};

    for (var w = node.firstChild; w; w = w.nextSibling){
        if (w.nodeType != 1)
            continue;
        else {
            if (!rules[w[ppc.TAGNAME]])
                rules[w[ppc.TAGNAME]] = [];
            rules[w[ppc.TAGNAME]].push(w);
        }
    }

    return rules;
};
//#endif

ppc.isCoord = function (n){
    return n || n === 0;
};

ppc.getCoord = function (n, other){
    return n || n === 0 ? n : other;
};

/**
 * @private
 */
ppc.getBox = function(value, base){
    if (!base) base = 0;

    if (value == null || (!parseInt(value) && parseInt(value) != 0))
        return [0, 0, 0, 0];

    var x = String(value).splitSafe(" ");
    for (var i = 0; i < x.length; i++)
        x[i] = parseInt(x[i]) || 0;
    switch (x.length) {
        case 1:
            x[1] = x[0];
            x[2] = x[0];
            x[3] = x[0];
            break;
        case 2:
            x[2] = x[0];
            x[3] = x[1];
            break;
        case 3:
            x[3] = x[1];
            break;
    }

    return x;
};

/**
 * @private
 */
ppc.getNode = function(data, tree){
    var nc = 0;//nodeCount
    //node = 1
    if (data != null) {
        for (var i = 0; i < data.childNodes.length; i++) {
            if (data.childNodes[i].nodeType == 1) {
                if (nc == tree[0]) {
                    data = data.childNodes[i];
                    if (tree.length > 1) {
                        tree.shift();
                        data = this.getNode(data, tree);
                    }
                    return data;
                }
                nc++
            }
        }
    }

    return null;
};

/**
 * Retrieves the first XML node with a `nodeType` of 1 from the children of an XML element.
 * @param {XMLElement} xmlNode The XML element that is the parent of the element to select.
 * @return {XMLElement} The first child element of the XML parent.
 * @throws An error when no child element is found.
 */
ppc.getFirstElement = function(xmlNode){
    // #ifdef __DEBUG
    try {
        xmlNode.firstChild.nodeType == 1
            ? xmlNode.firstChild
            : xmlNode.firstChild.nextSibling
    }
    catch (e) {
        throw new Error(ppc.formatErrorString(1052, null,
            "Xml Selection",
            "Could not find element:\n"
            + (xmlNode ? xmlNode.xml : "null")));
    }
    // #endif

    return xmlNode.firstChild.nodeType == 1
        ? xmlNode.firstChild
        : xmlNode.firstChild.nextSibling;
};

/**
 * Retrieves the last XML node with `nodeType` of 1 from the children of an XML element.
 * @param {XMLElement} xmlNode The XML element that is the parent of the element to select.
 * @return {XMLElement} The last child element of the XML parent.
 * @throw An error when no child element is found.
 */
ppc.getLastElement = function(xmlNode){
    // #ifdef __DEBUG
    try {
        xmlNode.lastChild.nodeType == 1
            ? xmlNode.lastChild
            : xmlNode.lastChild.nextSibling
    }
    catch (e) {
        throw new Error(ppc.formatErrorString(1053, null,
            "Xml Selection",
            "Could not find last element:\n"
            + (xmlNode ? xmlNode.xml : "null")));
    }
    // #endif

    return xmlNode.lastChild.nodeType == 1
        ? xmlNode.lastChild
        : xmlNode.lastChild.previousSibling;
};

/**
 * Selects the content of an HTML element. This currently only works in
 * Internet Explorer.
 * @param {HTMLElement} oHtml The container in which the content receives the selection.
 */
ppc.selectTextHtml = function(oHtml){
    if (!ppc.hasMsRangeObject) return;// oHtml.focus();

    var r = document.selection.createRange();
    try {r.moveToElementText(oHtml);} catch(e){}
    r.select();
};

// #endif
